package tech.mhuang.pacebox.core.compress.handler;

import tech.mhuang.pacebox.core.compress.CodeCompressHandler;
import tech.mhuang.pacebox.core.file.FileUtil;
import tech.mhuang.pacebox.core.io.IOUtil;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Enumeration;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;
import java.util.zip.ZipOutputStream;

/**
 * ZIP压缩解压类
 *
 * @author yuanhang.huang
 * @since 1.0.9
 */
public class ZipCodeCompressHandler implements CodeCompressHandler {

    /**
     * 后缀标识
     */
    public static final String SUFFIX = ".zip";

    /**
     * code标识
     */
    public static final String CODE = "ZIP";

    @Override
    public boolean match(String fileName) {
        return fileName.endsWith(SUFFIX);
    }

    @Override
    public void compress(String sourcePath, String destFile, boolean cover) throws IOException {
        zip(sourcePath, destFile, cover);
    }

    @Override
    public void decompress(String sourceFile, String destDir, boolean cover, boolean deep) throws IOException {
        unZip(sourceFile, destDir, cover, deep);
    }

    @Override
    public void compress(File sourcePath, OutputStream os) throws RuntimeException, IOException {
        zip(sourcePath, os);
    }

    /**
     * 压缩文件
     *
     * @param sourcePath 源文件/文件夹
     * @param destFile   压缩文件
     * @param cover      覆盖原文件
     * @throws RuntimeException RuntimeException
     * @throws IOException IOException
     */
    public void zip(String sourcePath, String destFile, boolean cover) throws RuntimeException, IOException {
        File source = new File(sourcePath);
        if (!source.exists()) {
            throw new FileNotFoundException(sourcePath + " is not found");
        }
        File dest = new File(destFile);
        if (!dest.exists() || dest.isDirectory()) {
            FileUtil.createFile(dest);
            zip(source, dest);
        } else if (dest.exists() && cover) {
            zip(source, dest);
        }
    }

    /**
     * 压缩文件
     *
     * @param sourcePath 源文件/文件夹
     * @param destFile   压缩文件
     * @throws RuntimeException RuntimeException
     * @throws IOException IOException
     */
    public void zip(File sourcePath, File destFile) throws RuntimeException, IOException {
        try (FileOutputStream fos = new FileOutputStream(destFile);) {
            zip(sourcePath, fos);
        }
    }

    /**
     * 压缩文件
     *
     * @param sourcePath 源文件/文件夹
     * @param os         输出流
     * @throws RuntimeException RuntimeException
     * @throws IOException IOException
     */
    public void zip(File sourcePath, OutputStream os) throws RuntimeException, IOException {
        try (ZipOutputStream zos = new ZipOutputStream(os)) {
            zip(sourcePath, zos);
        }
    }

    /**
     * 压缩文件
     *
     * @param sourcePath 源文件/文件夹
     * @param fos        文件输出流
     * @throws RuntimeException RuntimeException
     * @throws IOException IOException
     */
    public void zip(File sourcePath, FileOutputStream fos) throws RuntimeException, IOException {
        try (ZipOutputStream zos = new ZipOutputStream(fos)) {
            zip(sourcePath, zos);
        }
    }

    /**
     * 压缩文件
     *
     * @param sourcePath 源文件/文件夹
     * @param zos        压缩文件输出流
     * @throws IOException IOException
     */
    private void zip(File sourcePath, ZipOutputStream zos) throws IOException {
        zip(sourcePath, sourcePath.getName(), zos);
    }

    /**
     * 压缩文件
     *
     * @param sourcePath 源文件/文件夹
     * @param zipPath    文件在包内部的路径
     * @param zos        压缩文件输出流
     * @throws IOException IOException
     */
    private void zip(File sourcePath, String zipPath, ZipOutputStream zos) throws IOException {
        if (sourcePath.isFile()) {
            zipFile(sourcePath, zipPath, zos);
        } else {
            File[] files = sourcePath.listFiles();
            if (files == null || files.length == 0) {
                zipFile(sourcePath, zipPath, zos);
            } else {
                for (File file : files) {
                    String zipInnerPath = zipPath.concat(File.separator).concat(file.getName());
                    if (file.isFile()) {
                        zipFile(file, zipInnerPath, zos);
                    } else {
                        zip(file, zipInnerPath.concat(File.separator), zos);
                    }
                }
            }
        }
    }

    /**
     * 多文件压缩ZIP
     *
     * @param files 需要压缩的文件列表
     * @param zos   压缩文件输出流
     * @throws IOException 压缩失败会抛出运行时异常
     */
    public void zipFiles(File[] files, ZipOutputStream zos) throws IOException {
        for (File file : files) {
            zipFile(file, zos);
        }
    }

    /**
     * 单文件压缩ZIP
     *
     * @param file 源文件
     * @param zos  ZIP输出流
     * @throws IOException IOException
     */
    public void zipFile(File file, ZipOutputStream zos) throws IOException {
        if (file.isFile()) {
            zipFile(file, file.getName(), zos);
        } else {
            zipFile(file, file.getName().concat(File.separator), zos);
        }

    }

    /**
     * 单文件压缩ZIP
     *
     * @param file    源文件
     * @param zipPath 压缩包内的路径
     * @param zos     ZIP输出流
     * @throws IOException IOException
     */
    public void zipFile(File file, String zipPath, ZipOutputStream zos) throws IOException {
        zos.putNextEntry(new ZipEntry(zipPath));
        if (file.isFile()) {
            try (FileInputStream in = new FileInputStream(file)) {
                IOUtil.copyLarge(in, zos);
            }
        }
        zos.closeEntry();
    }

    /**
     * zip解压
     *
     * @param sourceFile zip源文件
     * @param destDir    解压后的目标文件夹
     * @param cover      覆盖文件
     * @param deep       深度解压（递归）
     * @throws IOException IO异常
     */
    public void unZip(String sourceFile, String destDir, boolean cover, boolean deep) throws IOException {
        unZip(new File(sourceFile), new File(destDir), cover, deep);
    }

    /**
     * zip解压
     *
     * @param sourceFile zip源文件
     * @param destDir    解压后的目标文件夹
     * @param cover      覆盖文件
     * @param deep       深度解压（递归）
     * @throws IOException IOException
     */
    public void unZip(File sourceFile, File destDir, boolean cover, boolean deep) throws IOException {
        if (!sourceFile.exists()) {
            throw new FileNotFoundException(sourceFile.getPath() + " is not found");
        }
        if (!destDir.exists()) {
            FileUtil.createDirectory(destDir, true);
        }
        try (ZipFile zipFile = new ZipFile(sourceFile)) {
            Enumeration<?> entries = zipFile.entries();
            while (entries.hasMoreElements()) {
                ZipEntry entry = (ZipEntry) entries.nextElement();
                String filePath = destDir + File.separator + entry.getName();
                File file = new File(filePath);
                if (file.exists() && !cover) {
                    continue;
                }
                /**
                 * 注意此处使用 JDK原生自带的entry.isDirectory() 判断压缩文件内的文件是否为文件夹是存在BUG的
                 * 此方法中使用的是endsWith("/")来判断是否为文件夹
                 * 而在windows中可能文件夹结尾是"\"，所以会导致判断错误
                 * 此处应当自行写条件判断
                 */
                if (entry.getName().endsWith("/") || entry.getName().endsWith("\\")) {
                    FileUtil.createDirectory(file, true);
                } else {
                    FileUtil.createFile(file);
                    try (InputStream is = zipFile.getInputStream(entry);
                         FileOutputStream fos = new FileOutputStream(file)) {
                        IOUtil.copyLarge(is, fos);
                    }
                    if (entry.getName().endsWith(SUFFIX) && deep) {
                        File chilDir = new File(file.getPath().substring(0, file.getPath().length() - SUFFIX.length()));
                        FileUtil.createDirectory(chilDir, true);
                        unZip(file, chilDir, cover, deep);
                    }
                }
            }
        }
    }

}
